﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Universe.MediaProbe.Utils;

namespace Universe.MediaProbe
{
    public class ScreenGrabber
    {

        public readonly ProbeReport Report;
        public OutputSize OutSize { get; set; }
        public OutputFormat OutFormat { get; set; }

        public ScreenGrabber(ProbeReport report)
        {
            if (report == null)
                throw new ArgumentNullException("report");

            Report = report;
        }

        public Screen Grab1Frame(decimal time)
        {
            var range = GrabRange(time, 1, 1);
            if (range.Count == 0)
                throw new InvalidOperationException("Screenshot expected");

            Screen ret = range[0];
            ret._Folder = Path.GetDirectoryName(ret._FullPath);
            return ret;
        }

        public ScreenList GrabRange(decimal start, int frames, decimal frameRate)
        {
            int optionalWidth = 0;
            int optionalHeight = 0;
            if (OutSize != null)
            {
                var video = Report.Videos[0];
                if (OutSize.OptionalPercentage > 0)
                {
                    optionalWidth = (int)(1m * video.Width * OutSize.OptionalPercentage / 100m);
                    optionalHeight = (int)(1m * video.Height * OutSize.OptionalPercentage / 100m);
                    optionalWidth = Math.Max(1, optionalWidth);
                    optionalHeight = Math.Max(1, optionalHeight);
                }
                else if (OutSize.OptionalHeight == 0 && OutSize.OptionalWidth > 0)
                {
                    optionalWidth = OutSize.OptionalWidth;
                    optionalHeight = (int)(1m * OutSize.OptionalWidth/video.Width * video.Height);
                    optionalWidth = Math.Max(1, optionalWidth);
                    optionalHeight = Math.Max(1, optionalHeight);
                }
                else if (OutSize.OptionalHeight > 0 && OutSize.OptionalWidth == 0)
                {
                    optionalHeight = OutSize.OptionalHeight;
                    optionalWidth = (int)(1m * OutSize.OptionalHeight  / video.Height * video.Width);
                    optionalWidth = Math.Max(1, optionalWidth);
                    optionalHeight = Math.Max(1, optionalHeight);
                }
                else if (OutSize.OptionalHeight > 0 && OutSize.OptionalWidth > 0)
                {
                    optionalHeight = OutSize.OptionalHeight;
                    optionalWidth = OutSize.OptionalWidth;
                }
                else
                {
                    optionalHeight = video.Height;
                    optionalWidth = video.Width;
                }
            }


            OutputFormat of = OutFormat ?? OutputFormat.JpegBest;
            var quality = Math.Min(31, Math.Max(2, of.Quality));
            string folder;
            var files = ScreenGrabImpl.Grab(
                Report.FullName, 
                start, frames, frameRate, 
                of.Encoder, quality,
                optionalWidth, optionalHeight,
                out folder);
            
            List<Screen> ret = new List<Screen>();
            int n = 0;
            foreach (string file in files)
            {
                ret.Add(Screen.Create(file, n, start + n * frameRate, of.Encoder));
                n++;
            }

            return ScreenList.Create(ret, folder);
        }


        public class Screen : IDisposable
        {
            internal string _FullPath;
            internal string _Folder;

            public int Index { get; set; }
            public Decimal Time { get; set; }
            public OutputEncoder Codec { get; set; }

            public string MimeType
            {
                get { return "image/" + Codec.ToString().ToLower().Replace("jpg", "jpeg"); }
            }
            
            public static Screen Create(string fullName, int index, decimal time, OutputEncoder codec)
            {
                return new Screen() {_FullPath = fullName, Index = index, Time = time};
            }

            public void Dispose()
            {
                string fullPath = _FullPath;
                if (fullPath != null && File.Exists(fullPath))
                {
                    File.Delete(fullPath);
                    _FullPath = null;
                }

                var folder = _Folder;
                if (folder != null && Directory.Exists(folder))
                {
                    Directory.Delete(folder);
                    _Folder = null;
                }
            }

            // reusable
            public void CopyTo(Stream output)
            {
                using (FileStream fs = new FileStream(_FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                {
                    StreamExtentions.Copy(fs, output);
                }
            }
        }

        public class ScreenList : Collection<Screen>, IDisposable
        {
            private string _Path;

            public ScreenList()
            {
            }

            public static ScreenList Create(IEnumerable<Screen> list, string path) 
            {
                if (list == null)
                    throw new ArgumentNullException("list");

                if (path == null)
                    throw new ArgumentNullException("path");

                ScreenList ret = new ScreenList() {_Path = path};
                foreach (var screen in list)
                    ret.Add(screen);

                return ret;
            }

            public void Dispose()
            {
                foreach (Screen s in this)
                    s.Dispose();

                string path = _Path;
                if (path != null && Directory.Exists(path))
                {
                    Directory.Delete(path);
                    _Path = null;
                }
            }
        }



        [Serializable]
        public class OutputSize
        {
            // Zero means 'optional'
            public int OptionalWidth { get; set; }
            public int OptionalHeight { get; set; }
            public int OptionalPercentage { get; set; }

            public static OutputSize PercentsAre(int percentage)
            {
                if (percentage < 1 || percentage > 100)
                    throw new ArgumentOutOfRangeException("percentage");

                return new OutputSize() { OptionalPercentage = percentage };
            }

            public static OutputSize WidthIs(int width)
            {
                if (width < 1)
                    throw new ArgumentOutOfRangeException("width");

                return new OutputSize() { OptionalWidth = width };
            }

            public static OutputSize HeightIs(int height)
            {
                if (height < 1)
                    throw new ArgumentOutOfRangeException("height");

                return new OutputSize() { OptionalHeight = height };
            }
        }

        [Serializable]
        public class OutputFormat
        {
            public OutputEncoder Encoder { get; set; }
            
            // jpeg only, 1 ... 31
            public int Quality { get; set; }

            public static OutputFormat JpegBest
            {
                get { return new OutputFormat() { Encoder = OutputEncoder.Jpeg, Quality = 2 }; }
            }

            public static OutputFormat JpegHigh
            {
                get { return new OutputFormat() { Encoder = OutputEncoder.Jpeg, Quality = 3 }; }
            }

            public static OutputFormat JpegMedium
            {
                get { return new OutputFormat() { Encoder = OutputEncoder.Jpeg, Quality = 8 }; }
            }

            public static OutputFormat JpegLow
            {
                get { return new OutputFormat() { Encoder = OutputEncoder.Jpeg, Quality = 20 }; }
            }

            public static OutputFormat JpegWorst
            {
                get { return new OutputFormat() { Encoder = OutputEncoder.Jpeg, Quality = 31 }; }
            }

            public static OutputFormat Png
            {
                get { return new OutputFormat() { Encoder = OutputEncoder.Png }; }
            }

            public static OutputFormat Gif
            {
                get { return new OutputFormat() { Encoder = OutputEncoder.Gif }; }
            }

            public static OutputFormat Bmp
            {
                get { return new OutputFormat() { Encoder = OutputEncoder.Bmp }; }
            }

        }

        [Serializable]
        public enum OutputEncoder
        {
            Jpeg = 1,
            Jpg = 1,
            Png = 2,
            Gif = 3,
            Bmp = 4,
/*
 * .NET doesnt understand :(         
 * Tiff = 5,
 * Tif = 5,
*/
        }
    }
}
